► マリウス . A GTK4 ssh-askpass in Zig
Source: https://xn--gckvb8fzb.com/a-gtk4-ssh-askpass-in-zig/
Method: legacy
Fetched: 2026-07-30T20:09:31.375792+00:00
IPFS: QmQVc5F1BRmZmfnvqUbd... | Open Raw
Cache: Freshly fetched
Every ssh-askpass in theGentootree pulls inX11or a fullKDEstack,
so I wrote my own withZig0.16 andGTK4, with hand-written bindings that
keepXout of the build.
I runhardenedGentooon my laptop, and most of the time I
never touchssh-askpassbecause I’m using-skkeys for most of the systems.
There is one class of situation where I do need it, though, which is when a
program wants an SSH key passphrase for a regular ED25519 key, but has no
terminal to read it from. The usual case isgo get, or thegotoolchain in
general, fetching a private module over SSH during a build that runs without a
TTY.OpenSSHcan’t prompt on a pipe, so it runs whateverSSH_ASKPASSpoints
at and puts the passphrase prompt in a window instead. For years I had nothing
installed for that and had to work around these scenarios. The main reason for
that is whatGentoo’sPortageoffers:
Each of these has at least one inconvenience I didn’t feel like putting up with.
My system runs with the global-XUSE flag, so anything that needsX11is
out before I look any further. Of the five,kde-plasma/ksshaskpassis the only
one withnoX11dependency whatsoever, which should have made it the
obvious pick, but the trouble is everything else that comes with it. As aSway user, I did not want a fullKDEstack on the machine just to
type the occasional passphrase, and that is what aksshaskpassinstall pulls
in:
lxqt-base/lxqt-openssh-askpassis next, and it needsXoutright. On top of
that it pulls in a fewKDEframework packages and a Qt built withXsupport,
which collides with thedev-qt/qtbasealready on my system that was compiled-X, so Portage stops on a slot conflict:
net-misc/ssh-askpass-fullscreenneedsXas well, this time by way ofGTK2and a Cairo built withXsupport:
net-misc/x11-ssh-askpassisX11by name, so no surprise there, and it also
needs the old imake build system, namelyx11-misc/xorg-cf-filesandx11-misc/imake, to compile at all:
That leftnet-misc/gnome-ssh-askpass. At first glance it looked like the one
option that needed noXat all, but that turned out to be wrong. It does needX11, and the ebuild appears to be broken about it. The build callspkg-config --libs gtk+-3.0 x11and the source includesgdk/gdkx.h, anX-only GDK header, so on a system compiled withoutXit fails to build:
This is where I gave up on the packaged options. Even setting theX11question
aside, every one of these usesGTK2orGTK3at most. However, it just so
happened that I had wanted to build something withGTK4for a long time, so
instead of patching one of the existing implementations, which are mostly C
anyway, I wrote my own withZig0.16 andGTK4, and called itssh-askpass-zigtk.
AvoidingX11
The reason theGTKhelpers break on my system is the headers. The standard way
of callingGTKincludes theGTK4headers, which pull inGDK, andGDKstill shipsgdk/gdkx.hon most installs, so anX11header comes in whether
you want it or not.Zig’s@cImport, the obvious way to call a C library,
would do the same, because it pulls in exactly those headers. Sossh-askpass-zigtkdoesn’t@cImportanything.src/gtk.zigdeclares the
thirty-oddGTKandGLibfunctions the program calls by hand, as plainexternprototypes:
Nothing in that file names a symbol fromgdk/gdkx.horX11/Xlib.h, so the
compiler never sees anXheader, and the binary builds and runs against aGTK4that was compiled withoutX11. The oneX-adjacent value it needs, theEscapekeysym, is hardcoded as0xff1brather than pulled fromgdk/gdkkeysyms.h.
GTKis built onGObject, which does single inheritance by putting the parent
struct as the first member of the child, so a window, a box, a label, a password
entry and a button are all layout-compatible with aGtkWidget *at the ABI
boundary. On theZigside oneopaque {}type stands in for all of them, and
every widget function takes and returns the same*Widget, without a hierarchy
of wrapper types to model something the C ABI already flattens.
The parts that don’t touchGTK, the mapping ofSSH_ASKPASS_PROMPTto a
dialog type and the parsing of theGNOME_SSH_ASKPASS_*_COLORvariables, are insrc/root.zigwith unit tests, so they run underzig build testwith no
display and noGTKat all. Recoloring goes through a small CSS provider, sinceGTK4removedgtk_widget_modify_fgand_bg.
Cross-compiling
Because the bindings are hand-written externs and noGTKheaders enter the
build,Zigcan cross-compile the binary for any Linux architecture without aGTK4toolchain for that target. The only thing missing at link time is theGTK4shared library itself, and-Dgtk-stubcovers that, as it builds a tiny
stublibgtk-4.so.1whose exported symbols are all no-ops, links the executable
against that, and lets the target’s realGTK4resolve at runtime instead. The
release workflow uses this to produce binaries forx86_64,aarch64,armv7,riscv64,powerpc64le,i386,loongarch64ands390xfrom one machine,
none of which hasGTK4installed for the other seven.
Note:ssh-askpass-zigtkdoesn’t grab the keyboard as otheraskpassimplementations normally would. TheGTK3helper callsgdk_seat_grabso
another client can’t read the passphrase as you type it, but from what I see,GTK4dropped that interface and I believe that Wayland doesn’t let a client
grab the keyboard at all, so there is no portable way to do it withoutX11.
Hence theGNOME_SSH_ASKPASS_GRAB_SERVERandGNOME_SSH_ASKPASS_GRAB_POINTERvariables also have no effect.
The code is ontty.failand mirrored toGitHub, where each tagged release ships prebuilt Linux
binaries per architecture. To use it, put the binary somewhere on yourPATHand pointSSH_ASKPASSat it. For a terminal that means two lines in~/.profileor your shell’s startup file (e.g.~/.zshrcfor myfellow Zsh
users):
SSH_ASKPASS_REQUIRE=prefer, fromOpenSSH8.4 onward, tellsOpenSSHto use
the dialog even when a terminal is available, as long as a graphical session is
present. On a systemd user session, the same two variables go in~/.config/environment.d/ssh-askpass.confas plainKEY=VALUElines with an
absolute path, since that file neither expands~nor runs a shell. Log out and
back in, and the nextssh-add,gitpull orgo getthat needs a passphrase
without a terminal gets the dialog.
Enjoyed this?Please consider supporting my work.
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------